package game;

import com.crunch.core.GameState;
import com.crunch.core.Input;
import com.crunch.core.Renderable;
import com.crunch.core.Resizable;
import com.crunch.graphics.gpu.*;
import game.game.RoomState;
import resources.ResourceManager;

import javax.media.opengl.GL;
import javax.media.opengl.GL3;
import java.awt.*;
import java.awt.event.WindowEvent;
import java.io.IOException;

/**
 * Holds resources which should be available for the whole game, like the resource manager.
 */
public class CoreState extends GameState {
	public CoreState() {
		super(-10);
	}

	@Override
	public void onCreate(Object... args) {
		gameSettings = new GameSettings();
		gameSettings.load();

		resourceManager = new ResourceManager(getEngine().window.renderer);

		resizable = new Resizable() {
			@Override
			public void resize(GpuState gpuState, int x, int y, int width, int height) {
				resizeSRGBFramebuffer(gpuState);
			}
		};
		getEngine().window.renderer.register(resizable);
		frameInitializer = new Renderable() {
			@Override
			public void render(GpuState gpuState) {
				initFrame(gpuState);
			}

			@Override
			public int getDepth() {
				return Integer.MIN_VALUE;
			}
		};
		getEngine().window.renderer.register(frameInitializer);
		blitter = new Renderable() {
			@Override
			public void render(GpuState gpuState) {
				blitSRGBFramebuffer(gpuState);
			}

			@Override
			public int getDepth() {
				return Integer.MAX_VALUE;
			}
		};
		getEngine().window.renderer.register(blitter);

		// LOAD RESOURCES:
		try {
			resourceManager.loadProgram("bone",
					getClass().getResource("/resources/programs/bone_vs.txt"),
					getClass().getResource("/resources/programs/bone_gs.txt"),
					getClass().getResource("/resources/programs/bone_fs.txt"));
			resourceManager.loadProgram("skin",
					getClass().getResource("/resources/programs/skin_vs.txt"),
					getClass().getResource("/resources/programs/skin_gs.txt"),
					getClass().getResource("/resources/programs/skin_fs.txt"));
			resourceManager.loadProgram("background",
					getClass().getResource("/resources/programs/background_vs.txt"),
					getClass().getResource("/resources/programs/background_gs.txt"),
					getClass().getResource("/resources/programs/background_fs.txt"));
			resourceManager.loadProgram("tileLayer",
					getClass().getResource("/resources/programs/tiles_vs.txt"),
					getClass().getResource("/resources/programs/tiles_gs.txt"),
					getClass().getResource("/resources/programs/tiles_fs.txt"));
			resourceManager.loadProgram("sprite",
					getClass().getResource("/resources/programs/sprite_vs.txt"),
					getClass().getResource("/resources/programs/sprite_gs.txt"),
					getClass().getResource("/resources/programs/sprite_fs.txt"));
			resourceManager.loadProgram("text",
					getClass().getResource("/resources/programs/text_vs.txt"),
					null,
					getClass().getResource("/resources/programs/text_fs.txt"));
			resourceManager.loadProgram("rect",
					getClass().getResource("/resources/programs/rect_vs.txt"),
					getClass().getResource("/resources/programs/rect_gs.txt"),
					getClass().getResource("/resources/programs/rect_fs.txt"));
			resourceManager.loadProgram("line",
					getClass().getResource("/resources/programs/line_vs.txt"),
					getClass().getResource("/resources/programs/line_gs.txt"),
					getClass().getResource("/resources/programs/line_fs.txt"));
			resourceManager.loadProgram("capsule",
					getClass().getResource("/resources/programs/capsule_vs.txt"),
					getClass().getResource("/resources/programs/capsule_gs.txt"),
					getClass().getResource("/resources/programs/capsule_fs.txt"));

			resourceManager.loadTexture("actor",
					getClass().getResource("/resources/textures/actor.png"));

			resourceManager.loadTexture("background",
					getClass().getResource("/resources/textures/background.png"));
			resourceManager.loadTexture("sky",
					getClass().getResource("/resources/textures/sky.png"));
			resourceManager.loadTexture("tileset",
					getClass().getResource("/resources/textures/tileset.png"));
			resourceManager.loadTexture("metalTileset",
					getClass().getResource("/resources/textures/metal.png"));
			resourceManager.loadTexture("font",
					getClass().getResource("/resources/textures/font.png"));

			resourceManager.loadTexture("player",
					getClass().getResource("/resources/textures/player.png"));
		} catch (IOException e) {
			System.out.println(e.getMessage());
			System.out.println("Failed to load resource(s)");
			getEngine().shutdown();
		}

		// listen for quit button
		Input.EventListener quitListener = new Input.EventListener() {
			@Override
			public void processEvent(AWTEvent e, int eventType) {
				if (eventType == Input.WINDOW_EVENT) {
					WindowEvent ev = (WindowEvent) e;
					if (ev.getID() == WindowEvent.WINDOW_CLOSING) {
						getEngine().shutdown();
					}
				}
			}
		};
		quitListener.eventMask = Input.WINDOW_EVENT;
		getEngine().input.addEventListener(quitListener);

		// resize window - todo: make these properties read from file
		gameWidth = 320;
		gameHeight = 240;
		windowScale = 3;
		getEngine().window.canvas.setPreferredSize(new Dimension(gameWidth*windowScale, gameHeight*windowScale));
		getEngine().window.pack();
	}

	private boolean firstFrame = true;
	@Override
	public void onUpdate() {
		if (firstFrame) {
			// create game state next frame, so that GPU resources can be loaded
			// pass core state as first argument
			getEngine().createGameState(RoomState.class, true, this);
			firstFrame = false;
		}
	}

	@Override
	public void onDestroy() {
		resourceManager.freeResources();

		getEngine().window.renderer.unregister(resizable);
		getEngine().window.renderer.unregister(frameInitializer);
		getEngine().window.renderer.unregister(blitter);
	}

	public GameSettings gameSettings;
	public ResourceManager resourceManager;

	public int getGameWidth() {
		return gameWidth;
	}

	public int getGameHeight() {
		return gameHeight;
	}

	public int getWindowScale() {
		return windowScale;
	}

	private void setupSRGBFramebuffer(GpuState gpuState) {
		gpuState.setFramebufferSRgbEnabled(true);

		framebuffer = gpuState.createFramebuffer();
		GpuTexture.Texture2DDescription desc = new GpuTexture.Texture2DDescription();
		desc.imageDescription.internalFormat = GL3.GL_SRGB;
		desc.mipmapLevels = new GpuTexture.Data2DDescription[] { new GpuTexture.Data2DDescription() };
		desc.mipmapLevels[0].width = gameWidth;
		desc.mipmapLevels[0].height = gameHeight;
		sRGBBuffer = gpuState.createTexture(desc);

		framebuffer.attach(GL.GL_COLOR_ATTACHMENT0, sRGBBuffer);
		framebuffer.bind(GL.GL_FRAMEBUFFER);
	}

	private void destroySRGBFramebuffer(GpuState gpuState) {
		if (framebuffer == null) {
			return;
		}
		gpuState.unbindFramebuffer(GL.GL_FRAMEBUFFER);
		framebuffer.release();
		sRGBBuffer.release();
		framebuffer = null;
		sRGBBuffer = null;
	}

	private void resizeSRGBFramebuffer(GpuState gpuState) {
		destroySRGBFramebuffer(gpuState);
		setupSRGBFramebuffer(gpuState);
	}

	private void initFrame(GpuState gpuState) {
		framebuffer.bind(GL.GL_FRAMEBUFFER);
		GpuViewportState vs = gpuState.createViewportState();
		vs.setViewport(0, 0, gameWidth, gameHeight);
		vs.bind();

		GpuDrawCall.ClearCall clearCall = gpuState.createClearCall();
		clearCall.setClearColor(new float[] { 0.0f, 0.0f, 0.0f, 0.0f });
		clearCall.setClearBuffers(GL.GL_COLOR_BUFFER_BIT);
		clearCall.clear();
	}

	private void blitSRGBFramebuffer(GpuState gpuState) {
		gpuState.unbindFramebuffer(GL.GL_FRAMEBUFFER);
		framebuffer.bind(GL3.GL_READ_FRAMEBUFFER);

		// blit - should probably eventually incorporate this into the GPU API
		GpuViewportState vs = gpuState.createViewportState();
		vs.setViewport(0, 0, gameWidth*windowScale, gameHeight*windowScale);
		vs.bind();
		gpuState.getGl().glBlitFramebuffer(0, 0, gameWidth, gameHeight,
				vs.getViewportX(), vs.getViewportY(),
				vs.getViewportX() + vs.getViewportWidth(),
				vs.getViewportY() + vs.getViewportHeight(),
				GL.GL_COLOR_BUFFER_BIT, GL.GL_NEAREST);

		framebuffer.bind(GL.GL_FRAMEBUFFER);
	}

	private void bindSRGBFramebuffer(GpuState gpuState) {
		framebuffer.bind(GL3.GL_FRAMEBUFFER);

		GpuViewportState vs = gpuState.createViewportState();
		vs.setViewport(0, 0, gameWidth, gameHeight);
		vs.bind();
	}

	private Resizable resizable;
	private Renderable frameInitializer;
	private Renderable blitter;
	private GpuFramebuffer framebuffer;
	private GpuTexture sRGBBuffer;

	private int gameWidth;
	private int gameHeight;
	private int windowScale;
}
